package cool.tianyou.blue.bird.module.auth.service;

import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.map.MapBuilder;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import cool.tianyou.blue.bird.common.exception.SecurityAssessException;
import cool.tianyou.blue.bird.common.mapper.DeviceMapper;
import cool.tianyou.blue.bird.common.mapper.UserMapper;
import cool.tianyou.blue.bird.common.model.contstant.QrcodeStatusEnum;
import cool.tianyou.blue.bird.common.model.entity.QrcodeLoginInfo;
import cool.tianyou.blue.bird.common.model.entity.ScanResult;
import cool.tianyou.blue.bird.common.model.po.Device;
import cool.tianyou.blue.bird.common.model.po.LoginUser;
import cool.tianyou.blue.bird.common.model.po.User;
import cool.tianyou.blue.bird.common.model.vo.UserVO;
import cool.tianyou.blue.bird.common.tool.LocalCache;
import cool.tianyou.blue.bird.common.tool.UserHolder;
import cool.tianyou.blue.bird.common.util.Assert;
import cool.tianyou.blue.bird.common.util.ZxingUtil;
import cool.tianyou.blue.bird.config.PrometheusCustomMonitor;
import cool.tianyou.blue.bird.module.auth.model.dto.LoginDTO;
import cool.tianyou.blue.bird.module.synchronize.service.DeviceService;
import cool.tianyou.blue.bird.module.synchronize.service.JmsService;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;
import tk.mybatis.mapper.entity.Example;
import tk.mybatis.mapper.util.Sqls;

/**
 * @author Liutianyou
 * @date 2021/2/16 6:18 下午
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class UserService {

  private final UserMapper userMapper;
  private final DeviceMapper deviceMapper;
  private final TimedCache<String, QrcodeLoginInfo> cache;
  private final BCryptPasswordEncoder passwordEncoder;
  private final DeviceService deviceService;
  private final LocalCache localCache;
  private final JmsService jmsService;
  private final PrometheusCustomMonitor monitor;

  @PostConstruct
  public void setGauge() {
    monitor.userCount(this.userMapper.count());
  }


  public String getToken(User user, String deviceName) {
    long date = System.currentTimeMillis();
    String token = "";
    token = JWT.create()
        .withAudience(user.getId() + "#" + user.getEmail() + "#" + date + "#" + deviceName)
        .sign(Algorithm.HMAC256(user.getPassword()));
    return token;
  }

  public LoginDTO login(UserVO user) {
    Example example = new Example(User.class);
    log.info("密码:明文：{},密文：{}", user.getPassword(), passwordEncoder.encode(user.getPassword()));
    example.createCriteria().andEqualTo("email", user.getEmail());
    List<User> users = userMapper.selectByExample(example);
    Assert.isAuth(!users.isEmpty(), "账号或密码错误");
    String encodePassword = users.get(0).getPassword();
    Assert.isAuth(passwordEncoder.matches(user.getPassword(), encodePassword), "密码错误");
    LoginDTO loginDTO = doLogin(user, users.get(0), false);
    jmsService.loginSuccessMessage(loginDTO.getDevice(), loginDTO.getIsNewDevice());
    return loginDTO;
  }

  public void logOut() {
    LoginUser loginUser = UserHolder.getLoginUser();
    localCache.removeByDeviceId(loginUser.getUserId(), loginUser.getDeviceId());
    deviceService.offLineDevice(loginUser.getDeviceId(), loginUser.getUserId());
  }

  private LoginDTO doLogin(UserVO user, User userPo, boolean qrcodeLogin) {
    String token = getToken(userPo, user.getDeviceUid());
    log.info("生成Token:{}", token);
    userMapper.updateByPrimaryKeySelective(userPo);
    //查询用户的设备信息
    List<Device> devices = deviceMapper.selectByExample(Example.builder(Device.class)
        .where(Sqls.custom()
            .andEqualTo("deviceName", user.getDeviceUid())
            .andEqualTo("userId", userPo.getId())
            .andEqualTo("type", user.getType())
        ).build());
    Device device = null;
    boolean isNewDevice = false;
    if (devices.isEmpty()) {
      //创建一个设备信息
      device = Device.builder()
          .deviceName(user.getDeviceUid()).type(user.getType())
          .userId(userPo.getId())
          .lastLoginTime(new Date()).disabled(false).alias(user.getBrand()).online(true)
          .build();
      deviceMapper.insert(device);
      isNewDevice = true;
    } else {
      //修改设备的登录信息
      device = devices.get(0);
      device.setLastLoginTime(new Date());
      device.setOnline(true);
      if (qrcodeLogin) {
        //二维码登陆设置 设备锁定状态为 false
        device.setDisabled(false);
      }
      deviceMapper.updateByPrimaryKeySelective(device);
    }
    deviceService.releaseCache(userPo.getId());
    LoginUser loginUser = new LoginUser(userPo, device, token);
    UserHolder.setUser(loginUser);
    localCache.put(token, loginUser);
    return new LoginDTO(token, device, isNewDevice);
  }


  public void authentication(String token) {
    Optional<LoginUser> loginUser = localCache.get(token);
    loginUser.orElseThrow(() -> new SecurityAssessException("登录失效，请重新登录"));
  }


  public Map<Object, Object> qrcode(String deviceName, String brand, String type) {
    try {
      File path = new File(ResourceUtils.getURL("classpath:").getPath());
      if (!path.exists()) {
        path = new File("");
      }
      File upload = new File(path.getAbsolutePath(), "static/img/qrcode/");
      if (!upload.exists()) {
        upload.mkdirs();
      }
      final String uuid = UUID.randomUUID().toString().replace("-", "");
      log.info("二维码UUID:{}", uuid);
      final String fileName = ZxingUtil.generate(uuid, upload.getAbsolutePath());
      final QrcodeLoginInfo qrcodeLoginInfo = new QrcodeLoginInfo(uuid,
          upload.getAbsolutePath() + File.separator + fileName,
          deviceName, brand, type, QrcodeStatusEnum.CREATED, null);
      cache.put(uuid, qrcodeLoginInfo);
      return MapBuilder.create().put("uuid", uuid).put("url", "/img/qrcode/" + fileName).build();
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    }
    return null;
  }

  public ScanResult qrcodeInfo(String uuid) {
    LoginUser loginUser = null;
    try {
      loginUser = UserHolder.getLoginUser();
    } catch (Exception e) {
      //TODO 暂时忽略异常
    }
    final QrcodeLoginInfo info = cache.get(uuid, false);
    if (info == null) {
      return new ScanResult(QrcodeStatusEnum.EXPIRED);
    }
    if (info.getQrcodeStatusEnum() == QrcodeStatusEnum.CREATED && loginUser != null) {
      info.setQrcodeStatusEnum(QrcodeStatusEnum.SCANNED);
    }

    return new ScanResult(info, uuid);
  }


  public LoginDTO loginConfirm(String uuid) {
    final QrcodeLoginInfo info = cache.get(uuid, false);
    Assert.notNull(info, "二维码过期，请刷新后重试");
    Integer userId = UserHolder.getLoginUser().getUserId();
    final LoginDTO dto = this
        .doLogin(info.getUserVO(), userMapper.selectByPrimaryKey(userId), true);
    info.setLoginDto(dto);
    info.setQrcodeStatusEnum(QrcodeStatusEnum.CONFIRM);
    return dto;
  }

  public boolean register(UserVO userVO) {
    Assert.isFalse(userMapper.repeatEmail(userVO.getEmail()), "邮箱已被注册");
    final User user = new User().setEmail(userVO.getEmail())
        .setDeviceName(userVO.getDeviceUid())
        .setPassword(passwordEncoder.encode(userVO.getPassword()));
    try {
      return userMapper.insert(user) > 0;
    } catch (Exception e) {
      log.error("注册失败", e);
      return false;
    }


  }
}
